/*
 * This file Copyright (C) Mnemosyne LLC
 *
 * This file is licensed by the GPL version 2. Works owned by the
 * Transmission project are granted a special exemption to clause 2(b)
 * so that the bulk of its code can remain under the MIT license.
 * This exemption does not extend to derived works not owned by
 * the Transmission project.
 *
 * $Id: notify.c 13265 2012-04-07 00:30:37Z jordan $
 */

#include <string.h> /* strcmp() */

#include <gio/gio.h>

#include <glib/gi18n.h>
#include "conf.h"
#include "notify.h"
#include "tr-prefs.h"
#include "util.h"

#define NOTIFICATIONS_DBUS_NAME           "org.freedesktop.Notifications"
#define NOTIFICATIONS_DBUS_CORE_OBJECT    "/org/freedesktop/Notifications"
#define NOTIFICATIONS_DBUS_CORE_INTERFACE "org.freedesktop.Notifications"

static GDBusProxy *proxy = NULL;
static GHashTable *active_notifications = NULL;
static gboolean server_supports_actions = FALSE;

typedef struct _TrNotification
{
    guint    id;
    TrCore * core;
    int      torrent_id;
} TrNotification;

static void
tr_notification_free( gpointer data )
{
    TrNotification * n = data;
    if( n->core )
        g_object_unref( G_OBJECT( n->core ) );
    g_free( n );
}

static void
get_capabilities_callback( GObject      * source,
                           GAsyncResult * res,
                           gpointer       user_data UNUSED )
{
    GVariant  *result;
    char     **caps;
    int        i;

    result = g_dbus_proxy_call_finish( G_DBUS_PROXY( source ), res, NULL );
    if( !result || !g_variant_is_of_type( result, G_VARIANT_TYPE( "(as)" ) ) )
    {
        if( result )
            g_variant_unref( result );
        return;
    }

    g_variant_get( result, "(^a&s)", &caps );
    for( i = 0; caps[i]; i++ )
    {
        if( strcmp( caps[i], "actions" ) == 0 )
        {
            server_supports_actions = TRUE;
            break;
        }
    }
    g_free( caps );
    g_variant_unref( result );
}

static void
g_signal_callback( GDBusProxy * proxy UNUSED,
                   char       * sender_name UNUSED,
                   char       * signal_name,
                   GVariant   * params,
                   gpointer     user_data UNUSED )
{
    TrNotification * n;
    guint id;

    g_return_if_fail( g_variant_is_of_type( params, G_VARIANT_TYPE( "(u*)" ) ) );

    g_variant_get( params, "(u*)", &id, NULL );
    n = g_hash_table_lookup( active_notifications,
                             GINT_TO_POINTER( (int *) &id ) );
    if( n == NULL )
        return;

    if( strcmp( signal_name, "NotificationClosed" ) == 0 )
    {
        g_hash_table_remove( active_notifications,
                             GINT_TO_POINTER( (int *) &n->id ) );
    }
    else if( strcmp( signal_name, "ActionInvoked" ) == 0 &&
             g_variant_is_of_type( params, G_VARIANT_TYPE( "(us)" ) ) )
    {
        char * action;
        tr_torrent * tor;

        tor = gtr_core_find_torrent( n->core, n->torrent_id );
        if( tor == NULL )
            return;

        g_variant_get( params, "(u&s)", NULL, &action );
        if( strcmp( action, "folder" ) == 0 )
        {
            gtr_core_open_folder( n->core, n->torrent_id );
        }
        else if( strcmp( action, "file" ) == 0)
        {
            const tr_info * inf = tr_torrentInfo( tor );
            const char * dir = tr_torrentGetDownloadDir( tor );
            char * path = g_build_filename( dir, inf->files[0].name, NULL );
            gtr_open_file( path );
            g_free( path );
        }
    }
}

static void
dbus_proxy_ready_callback( GObject      * source UNUSED,
                           GAsyncResult * res,
                           gpointer       user_data UNUSED )
{
    proxy = g_dbus_proxy_new_for_bus_finish( res, NULL );
    if( proxy == NULL )
    {
        g_warning( "Failed to create proxy for %s", NOTIFICATIONS_DBUS_NAME );
        return;
    }

    g_signal_connect( proxy, "g-signal",
                      G_CALLBACK( g_signal_callback ), NULL );
    g_dbus_proxy_call( proxy,
                       "GetCapabilities",
                       g_variant_new( "()" ),
                       G_DBUS_CALL_FLAGS_NONE, -1, NULL,
                       get_capabilities_callback, NULL );
}

void
gtr_notify_init( void )
{
    active_notifications = g_hash_table_new_full( g_int_hash, g_int_equal,
                                                  NULL, tr_notification_free );
    g_dbus_proxy_new_for_bus( G_BUS_TYPE_SESSION,
                              G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
                              NULL,
                              NOTIFICATIONS_DBUS_NAME,
                              NOTIFICATIONS_DBUS_CORE_OBJECT,
                              NOTIFICATIONS_DBUS_CORE_INTERFACE,
                              NULL, dbus_proxy_ready_callback, NULL );
}

static void
notify_callback( GObject      * source,
                 GAsyncResult * res,
                 gpointer       user_data )
{
    TrNotification * n = user_data;
    GVariant * result;

    result = g_dbus_proxy_call_finish( G_DBUS_PROXY( source ), res, NULL );
    if( !result || !g_variant_is_of_type( result, G_VARIANT_TYPE( "(u)" ) ) )
    {
        if( result )
            g_variant_unref( result );
        tr_notification_free( n );
        return;
    }

    g_variant_get( result, "(u)", &n->id );
    g_hash_table_insert( active_notifications,
                         GINT_TO_POINTER( ( int * )&n->id ), n );

    g_variant_unref( result );
}

void
gtr_notify_torrent_completed( TrCore * core, int torrent_id )
{
    GVariantBuilder actions_builder;
    TrNotification * n;
    tr_torrent * tor;
    const char * cmd = gtr_pref_string_get( PREF_KEY_TORRENT_COMPLETE_SOUND_COMMAND );

    if( gtr_pref_flag_get( PREF_KEY_TORRENT_COMPLETE_SOUND_ENABLED ) )
        g_spawn_command_line_async( cmd, NULL );

    if( ! gtr_pref_flag_get( PREF_KEY_TORRENT_COMPLETE_NOTIFICATION_ENABLED ) )
        return;

    g_return_if_fail( G_IS_DBUS_PROXY( proxy ) );

    tor = gtr_core_find_torrent( core, torrent_id );

    n = g_new0( TrNotification, 1 );
    n->core = g_object_ref( G_OBJECT( core ) );
    n->torrent_id = torrent_id;

    g_variant_builder_init( &actions_builder, G_VARIANT_TYPE( "as" ) );

    if( server_supports_actions )
    {
        const tr_info * inf = tr_torrentInfo( tor );
        if( inf->fileCount == 1 )
        {
            g_variant_builder_add( &actions_builder, "s", "file" );
            g_variant_builder_add( &actions_builder, "s", _( "Open File" ) );
        }
        else
        {
            g_variant_builder_add( &actions_builder, "s", "folder" );
            g_variant_builder_add( &actions_builder, "s", _( "Open Folder" ) );
        }
    }

    g_dbus_proxy_call( proxy,
                       "Notify",
                       g_variant_new( "(susssasa{sv}i)",
                                      "Transmission", n->id, "transmission",
                                      _( "Torrent Complete" ),
                                      tr_torrentName( tor ),
                                      &actions_builder, NULL, -1 ),
                       G_DBUS_CALL_FLAGS_NONE, -1, NULL,
                       notify_callback, n );
}

void
gtr_notify_torrent_added( const char * name )
{
    TrNotification * n;

    g_return_if_fail( G_IS_DBUS_PROXY( proxy ) );

    if( !gtr_pref_flag_get( PREF_KEY_TORRENT_ADDED_NOTIFICATION_ENABLED ) )
        return;

    n = g_new0( TrNotification, 1 );
    g_dbus_proxy_call( proxy,
                       "Notify",
                       g_variant_new( "(susssasa{sv}i)",
                                      "Transmission", 0, "transmission",
                                      _( "Torrent Added" ), name,
                                      NULL, NULL, -1 ),
                       G_DBUS_CALL_FLAGS_NONE, -1, NULL,
                       notify_callback, n );
}
